---
id: templateCompilation
title: vue 模版编译
---

前面讲了 vdom 在进行 diff 时是用 vnode，我们在写 vue 时`<template></template`模版需要编译成 vnode，来供页面渲染。


## 模版生命周期
### 创建过程 
- 生成模版

### 渲染过程

- 执行模板 render 函数
- 触发响应式，监听 data 属性 getter setter  

### 更新过程

- 触发 setter（此前在 getter中已被监听）  
- 重新执行 render 函数，生成 newVnode  
- patch(vnode, newVnade)


### 异步渲染  
- 汇总 data 的修改，一次性更新视图，汇总 data 的修改，一次性更新视图

## vue3 模版编译过程

Vue.js的模版编译过程可以简单地描述为以下几个步骤：

解析模版：Vue.js将模版解析成抽象语法树（AST）。  
静态优化：Vue.js通过静态分析抽象语法树，找出其中不需要响应式更新的节点，将它们标记为静态节点，以提高渲染性能。  
代码生成：Vue.js将抽象语法树转换为渲染函数，并将其编译为可执行的JavaScript代码。  
渲染：在运行时，Vue.js通过执行渲染函数生成虚拟DOM树，然后将其与旧的虚拟DOM树进行比较，找出需要更新的部分，最后将这些部分更新到实际的DOM元素上。  
在Vue.js的编译过程中，最重要的部分是生成渲染函数。渲染函数负责将Vue.js的响应式数据转换为实际的DOM元素，并处理事件、指令等其他特殊情况。  

如果你希望深入了解Vue.js的模版编译过程，可以查看Vue.js的官方文档，其中有更详细的介绍和示例代码。

## 演示 with 语句

模版编译内用到了 with 语句  
[with 语句定义](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/with)：扩展一个语句的作用域链。

下面这个例子：  
结果会打印"c"  
with 内传入了 obj，所以方法体内的的未声明变量都会先从 obj 上找，找不到就会再往上找。

```ts
<script>
    // 1.全局作用域下的abc变量
    let abc = "a";

    // 2.obj作用域下的abc变量
    let obj = { abc: "c" };

    // 立即执行函数
    (function fn() {
        // 3.函数作用域下的abc变量
        let abc = "b";

        with (obj) {
            console.log(abc); // c
        }
    })();
</script>

```

## 演示

### 环境准备

软件版本  
vue-template-compiler @2.6.14

```
1.新建个文件夹并进入   vue-template-compiler
2.初始化node项目  npm init -y
3.安装template编译的包  npm i vue-template-compiler@2.6.14
4.根目录新建个index.js
```

### vue with内部函数的作用

```
_b   ——>   bindObjectProps()
_c   ——>   createElement()
_d   ——>   bindDynamickeys()
_e   ——>   createEmptyVNode()
_f   ——>   resolveFilter()
_g   ——>   bindObjectListeners()
_i   ——>   looseIndexOf()
_k   ——>   checkKeyCodes()
_l   ——>   renderList()
_m   ——>   renderStatic()
_n   ——>   toNumber()
_o   ——>   markOnce()
_p   ——>   prependModifier()
_q   ——>   looseEqual()
_s   ——>   toString()
_t   ——>   renderslot()
_u   ——>   resolveScopedslots()
_v   ——>   createTextVNode()
```

## AST 抽象语法树
AST（Abstract Syntax Tree，抽象语法树）是源代码语法结构的一种抽象表示，它以树形结构来表示源代码的语法结构，每个节点表示源代码中的一个语法结构。AST 是编译器中的一个重要概念，在编译过程中，编译器会将源代码转换为 AST，然后根据 AST 进行代码优化、生成中间代码或目标代码。

在编程语言中，AST 通常是编译器前端的输出，也就是编译器将源代码转换成 AST 后，就可以进行代码优化、类型检查、错误提示等操作。AST 也是实现代码转换、重构、静态分析等工具的基础，例如 Babel 就是基于 AST 实现 JavaScript 代码的转换。

在前端开发中，AST 还经常被用于模板编译、静态类型检查、代码压缩等场景。例如，在 Vue 中，模板编译过程就是将模板转换为 AST，然后生成可执行的渲染函数。在 TypeScript 中，编译器会将 TypeScript 代码转换为 AST，并进行类型检查和转换为 JavaScript 代码。在代码压缩工具中，通常会将 JavaScript 代码转换为 AST，然后进行优化、删除无用代码、重写代码结构等操作，最终再将 AST 转换为压缩后的 JavaScript 代码。


### 插值的编译结果

```js
// index.js


const compiler = require('vue-template-compiler');

// 1. 编写模板
const template = '<div>{{userName}}</div>';

// 2. 进行编译
let comResult = compiler.compile(template);

// 3.打印编译结果
console.log(comResult);


// 输入 node index.js 查看编译的AST语法树
{
  ast: {
    type: 1,
    tag: 'p',
    attrsList: [],
    attrsMap: {},
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object] ],
    plain: true,
    static: false,
    staticRoot: false
  },
  render: "with(this){return _c('p',[_v(_s(message))])}",
  staticRenderFns: [],
  errors: [],
  tips: []
}

```

### 表达式的编译结果

```js
const compiler = require('vue-template-compiler');

// 1. 编写模板
const template = '<div>{{id > 0 ?"正确":"错误"}}</div>';

// 2. 进行编译
let comResult = compiler.compile(template);

// 3.打印编译结果
console.log(comResult);


// 输入 node index.js 查看结果
{
  ast: {
    type: 1,
    tag: 'div',
    attrsList: [],
    attrsMap: {},
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object] ],
    plain: true,
    static: false,
    staticRoot: false
  },
  render: `with(this){return _c('div',[_v(_s(id > 0 ?"正确":"错误"))])}`,
  staticRenderFns: [],
  errors: [],
  tips: []
}

```
### 属性的编译结果

```js
const compiler = require('vue-template-compiler');

// 1. 编写模板
const template = '<div id="div1" class="div1"><a :href="url"></a></div>';

// 2. 进行编译
let comResult = compiler.compile(template);

// 3.打印编译结果
console.log(comResult);


// 输入 node index.js 查看结果
{
  ast: {
    type: 1,
    tag: 'div',
    attrsList: [ [Object] ],
    attrsMap: { id: 'div1', class: 'div1' },
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object] ],
    plain: false,
    staticClass: '"div1"',
    attrs: [ [Object] ],
    static: false,
    staticRoot: false
  },
  render: `with(this){return _c('div',{staticClass:"div1",attrs:{"id":"div1"}},[_c('a',{attrs:{"href":url}})])}`,
  staticRenderFns: [],
  errors: [],
  tips: []
}

```

### 条件的编译结果

```js
const compiler = require('vue-template-compiler');

// 1. 编写模板
const template = `<ul>
                    <li v-if="flag">a</li>
                    <li v-else>b</li>
                  </ul>`;

// 2. 进行编译
let comResult = compiler.compile(template);

// 3.打印编译结果
console.log(comResult);

```

### 事件的编译结果

```js
const compiler = require('vue-template-compiler');

// 1. 编写模板
const template = `<div @click="openDetails">查看详情</div>`;

// 2. 进行编译
let comResult = compiler.compile(template);

// 3.打印编译结果
console.log(comResult);


// 输入 node index.js 查看结果
{
  ast: {
    type: 1,
    tag: 'ul',
    attrsList: [],
    attrsMap: {},
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object] ],
    plain: true,
    static: false,
    staticRoot: false
  },
  render: `with(this){return _c('ul',[(flag)?_c('li',[_v("a")]):_c('li',[_v("b")])])}`,
  staticRenderFns: [],
  errors: [],
  tips: []
}

// 输入 node index.js 查看结果
{
  ast: {
    type: 1,
    tag: 'div',
    attrsList: [ [Object] ],
    attrsMap: { '@click': 'openDetails' },
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object] ],
    plain: false,
    hasBindings: true,
    events: { click: [Object] },
    static: false,
    staticRoot: false
  },
  render: `with(this){return _c('div',{on:{"click":openDetails}},[_v("查看详情")])}`,
  staticRenderFns: [],
  errors: [],
  tips: []
}

```


### v-model的编译结果

```js
const compiler = require('vue-template-compiler');

// 1. 编写模板
const template = `<input type="text" v-model="userName" />`;

// 2. 进行编译
let comResult = compiler.compile(template);

// 3.打印编译结果
console.log(comResult);


// 输入 node index.js 查看结果
{
  ast: {
    type: 1,
    tag: 'input',
    attrsList: [ [Object], [Object] ],
    attrsMap: { type: 'text', 'v-model': 'userName' },
    rawAttrsMap: {},
    parent: undefined,
    children: [],
    plain: false,
    attrs: [ [Object] ],
    hasBindings: true,
    directives: [ [Object] ],
    static: false,
    staticRoot: false,
    props: [ [Object] ],
    events: { input: [Object] }
  },
  render: `with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(userName),expression:"userName"}],attrs:{"type":"text"},domProps:{"value":(userName)},on:{"input":function($event){if($event.target.composing)return;userName=$event.target.value}}})}`,
  staticRenderFns: [],
  errors: [],
  tips: []
}
```


## 测试

### 描述一下Vue 模板编译和 Vue  diff 算法执行时机可扩展描述？
时机： Vue 实例被创建时。模板编译是在应用程序的“创建”阶段执行的，将模板字符串编译成渲染函数。模板编译只会执行一次，生成的渲染函数会被重复使用来渲染组件的视图。

时机：应用程序的“更新”阶段。即组件的状态发生变化时，Vue 会使用 diff 算法来比较前后两个状态的差异，并更新组件的视图。每次更新时，Vue 都会重新执行 diff 算法。

更新阶段动作：Vue 会先根据模板中的指令、事件处理程序、表达式等内容生成新的 VNode（虚拟节点）树，然后使用 diff 算法比较新旧 VNode 树之间的差异，并只更新必要的部分，以提高更新性能。


